﻿using System;
using System.Collections.Generic;
using SeaBattle.Domain.Abstract;
using System.Linq;
using SeaBattle.Domain.Entities.Standart;


namespace SeaBattle.Domain.Entities
{
    public class Game
    {
        private IShipRepository shipRepository;

        private IFleetRepository fleetRepository;

        /// <summary>
        /// Constructor of the game
        /// </summary>
        /// <param name="id">Game ID</param>
        /// <param name="created">Time of creating</param>
        /// <param name="creator">Game creator</param>
        /// <param name="shipRepository">Ship repository.</param>
        public Game(int id, DateTime created, Player creator, IShipRepository shipRepository, IFleetRepository fleetRepository)
        {
            Id = id;
            Created = created;
            Creator = creator;
            State = GameState.STATE_CREATED;
            this.shipRepository = shipRepository;
            this.fleetRepository = fleetRepository;
            Navies = new Dictionary<Player, Fleet>();
            Navies.Add(creator, fleetRepository.CreateFleet());
            CreatorFleetInitialized = false;
            OpponentFleetInitialized = false;
            Shot = null;
        }

        private GameConfiguration configuration = new StandartGameConfiguration();

        public GameConfiguration Configuration
        {
            get { return configuration; }
            set
            {
                if (State != GameState.STATE_CREATED)
                    throw new ApplicationException("Can change game configuration only after creation.");
                configuration = (GameConfiguration)value.Clone();
            }
        }



        private Player winner;
        public Player Winner
        {
            get
            {
                return winner;
            }
            set
            {
                if ((value == Creator || value == Opponent) && State == GameState.STATE_PLAY)
                    winner = value;
            }
        }
        

        public bool CreatorFleetInitialized { get; private set; }
        public bool OpponentFleetInitialized { get; private set; }
        private Player opponent;        
        public int Id { get; private set; }
        private int preset;
        public int Preset 
        {
            get
            {
                return preset;
            }
            set
            {
                if (value > 0)
                    preset = value;
            }
        }
        /// <summary>
        /// Gets the created.
        /// </summary>
        public DateTime Created { get; private set; }
        /// <summary>
        /// Gets the creator.
        /// </summary>
        public Player Creator { get; private set; }
        /// <summary>
        /// Gets or sets the opponent.
        /// </summary>
        /// <value>
        /// The opponent.
        /// </value>
        public Player Opponent { get { return opponent; } set{SetOpponent(value);} }
        public GameState State { get; private set; }

        private void SetOpponent(Player theOpponent)
        {
            // The game must be in Opened status
            if(!Opened)
            {
                throw new Exception("The game "+Id+" not opened");
            }
            // Check whether Creator and Opponent are not the same persons
            if(Creator == theOpponent)
            {
                throw new Exception("The opponent is the same as creator of the game "+Id);
            }
            // Set opponent
            opponent = theOpponent;
            Navies.Add(opponent, fleetRepository.CreateFleet());
        }

        public void JoinOpponent(Player theOpponent)
        {
            Opponent = theOpponent;
            BoardPreparing = true;            
        }

        public bool Opened
        {
            get { return State == GameState.STATE_OPENED; }
            set
            {
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    if(State!=GameState.STATE_CREATED)
                        throw new ApplicationException("Can setup game only after its creating.");
                    if (configuration == null)
                        throw new ApplicationException("Game configuration has not been changed");
                    State = GameState.STATE_OPENED;
                }
            }
        }
        public bool Closed
        {
            get { return State == GameState.STATE_CLOSED; }
            set
            {
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    if (State != GameState.STATE_PLAY)
                        throw new ApplicationException("Game can be closed only after playing.");
                    State = GameState.STATE_CLOSED;
                }
            }
        }

        public bool BoardPreparing
        {
            get { return State == GameState.STATE_BOARD_SETUP; }
            private set
            {
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    if (Shot == null)
                        throw new ApplicationException("Not initialized shot during setupping."); 
                    if(State!=GameState.STATE_OPENED)
                        throw new ApplicationException("Can setup boards only after its opening.");
                    State = GameState.STATE_BOARD_SETUP;
                }
            }
        }

        public bool Playing
        {
            get { return State == GameState.STATE_PLAY; }
            private set
            {
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    if (State != GameState.STATE_BOARD_SETUP)
                        throw new ApplicationException("Can play only after its board setup.");
                    State = GameState.STATE_PLAY;
                }
            }
        }

        public Dictionary<Player, Fleet> Navies { get; set; }

        /// <summary>
        /// Add the ship
        /// </summary>
        /// <param name="player">Owner of the ship.</param>
        /// <param name="x">Ship X coordinate.</param>
        /// <param name="y">Ship Y coordinate.</param>
        /// <param name="PartsCount">Count of parts.</param>
        /// <param name="State">Vertical or horizontal.</param>
        public void AddShip(Player player, int x, int y, byte PartsCount, ShipState State)
        {
            if (!this.BoardPreparing)
                throw new Exception("Adding ship during wrong game state.");
            if (player != Opponent && player != this.Creator) 
                throw new Exception("Wrong player!");
            Navies[player].AddShip(new Ship(PartsCount,x,y,State),State);
        }

        public string TryPrepareFleet(Player player,string ShipPartPositions)
        {
            string St=configuration.IsCorrect(ShipPartPositions);
            if (St=="true")
            {
                ResetFleet(player);
                configuration.InitializeFleet(ShipPartPositions, Navies[player]);
                if (player == Creator)
                    CreatorFleetInitialized = true;
                else
                    OpponentFleetInitialized = true;
                if (CreatorFleetInitialized && OpponentFleetInitialized)
                    this.Playing = true;
            }
            return St;

        }

        private IShot shot=null;
        public IShot Shot 
        { 
            get
            {
                return shot;
            }
            set
            {
                if (shot != null)
                    throw new ApplicationException("Twice initializing shot.");
                this.shot = value;
                if(shot!=null)
                    shot.SetGame(this);
            }
        }        

        /// <summary>
        /// Removes the ship
        /// </summary>
        /// <param name="player">Owner of the ship</param>
        /// <param name="ShipID">Ship ID</param>
        public void RemoveShip(Player player,int ShipID)
        {
            if (!this.BoardPreparing)
                throw new Exception("Removing ship during wrong game state.");
            if (player != Opponent && player != this.Creator)
                throw new Exception("Wrong player!");
            Navies[player].RemoveShip(shipRepository.GetShip(ShipID));
        }

        /// <summary>
        /// Delete all ships.
        /// </summary>
        /// <param name="player">Owner of the navie.</param>
        public void ResetFleet(Player player)
        {
            if (!this.BoardPreparing)
                throw new Exception("WrongState!");
            if (player != Opponent && player != this.Creator)
                throw new Exception("Wrong player!");
            Fleet fleet = Navies[player];
            fleet.Clear();
        }

        public bool PlantedShips(Player player)
        {
            if (player == Creator)
                return CreatorFleetInitialized;
            else if (player == Opponent)
                return OpponentFleetInitialized;
            else
                return false;
        }
    }

    public enum GameState
    {        
        STATE_CREATED = 0,
        STATE_OPENED,
        STATE_BOARD_SETUP, // Setup game board
        STATE_PLAY,
        STATE_CLOSED
    }
}
